/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Controller;

import ControllerInterface.AbstractControllerMapFactory;
import ControllerInterface.IControllerCrawler;
import ControllerInterface.IControllerMainMapping;
import ControllerInterface.IControllerStatsMap;
import InterfaceCrawlerPlugin.ICrawlerPlugin;
import ModelInterface.AbstractModelMapFactory;
import ModelInterface.IModelCrawler;
import ViewInterface.AbstractViewMapFactory;
import ViewInterface.IViewCrawler;
import java.io.File;
import java.io.FilenameFilter;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Iterator;
import java.util.ServiceLoader;
import java.util.Vector;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 *
 * @author Fabian
 */
public class ControllerCrawler implements IControllerCrawler {

    private IViewCrawler vistaCrawler;
    private IModelCrawler modeloCrawler;
    private IControllerMainMapping myController;
    private IControllerStatsMap controladorStats;
    private static final String EXTENSION_JAR = ".jar";
    private static final String EXTENSION_XML = ".xml";
    private static final String DIRECTORIO_PLUGINS = "plugins/";
    private static final String DIRECTORIO_XML = "DataSource/";
    private static final String METODO_ADD_URL = "addURL";
    private static final Class[] PARAMETRO_METODO = new Class[]{URL.class};
    private boolean extractOK;
    private int idCrawler;
    private URLClassLoader loader;
    private Method metodoAdd;
    private ICrawlerPlugin[] pluginLoaded;
    private DefaultTreeModel modeloXML;
    Thread hiloCrawler;

    /**
     * carga los plugins encontrados al classpath
     *
     * @return true si se cargaron los plugins, false en caso de existir algun
     * error
     */
    public boolean cargarPlugins() {
        boolean cargados = true;
        try {
            //obtiene el listado de archivos .jar dentro del directorio
            File[] files = buscarArchivos(DIRECTORIO_PLUGINS, EXTENSION_JAR);

            if (files.length > 0) {
                ModificadorClassPath();

                //a cada jar lo incluye al classpath
                for (File file : files) {
                    try {
                        addArchivo(file);
                    } catch (MalformedURLException ex) {
                        System.err.println("URL incorrecta: "
                                + ex.getMessage());
                    }
                }
            }
        } catch (Exception ex) {
            cargados = false;
            System.err.println(ex.getMessage());
        }
        return cargados;
    }

    /**
     * Busca todos los jars de en el directorio de plugins
     *
     * @return jars del directorio de plugins
     */
    private static File[] buscarArchivos(String directorio, final String extension) {
        //crea lista vacia de archivos
        Vector<File> vUrls = new Vector<File>();

        //si existe el directorio "plugins" continua
        File directorioPlugins = new File(directorio);
        if (directorioPlugins.exists() && directorioPlugins.isDirectory()) {

            //obtiene todos los archivos con la extension .jar
            File[] jars = directorioPlugins.listFiles(new FilenameFilter() {

                @Override
                public boolean accept(File dir, String name) {
                    return name.endsWith(extension);
                }
            });
            vUrls.addAll(Arrays.asList(jars));
        }

        //retorna todos los archivos encontrados
        return vUrls.toArray(new File[0]);
    }

    /**
     * Obtiene todos los plugins IPluginMensaje encontrados en el classpath
     *
     * @return lista de plugins encontrados e instanciados
     */
    private static ICrawlerPlugin[] getPlugins() {

        //cargamos todas las implementaciones de IPluginMensaje
        //encontradas en el classpath
        ServiceLoader<ICrawlerPlugin> sl =
                ServiceLoader.load(ICrawlerPlugin.class);
        sl.reload();

        //crea una lista vacia de plugins IPluginMensaje
        Vector<ICrawlerPlugin> vAv = new Vector<ICrawlerPlugin>();

        //cada plugin encontrado es agregado a la lista
        for (Iterator<ICrawlerPlugin> it = sl.iterator(); it.hasNext();) {
            try {
                ICrawlerPlugin pl = it.next();
                vAv.add(pl);
            } catch (Exception ex) {
                System.err.println("Excepcion al obtener plugin: "
                        + ex.getMessage());
            }
        }

        //retorna los plugins encontrados y cargados
        return vAv.toArray(new ICrawlerPlugin[0]);
    }

    private void ModificadorClassPath() throws NoSuchMethodException {
        loader = (URLClassLoader) ClassLoader.getSystemClassLoader();
        metodoAdd = URLClassLoader.class.getDeclaredMethod(METODO_ADD_URL, PARAMETRO_METODO);
        metodoAdd.setAccessible(true);
    }

    private URL[] getURLs() {
        return loader.getURLs();
    }

    private void addURL(URL url) {
        if (url != null) {
            try {
                metodoAdd.invoke(loader, new Object[]{url});
            } catch (Exception ex) {
                System.err.println("Excepcion al guardar URL: " + ex.getLocalizedMessage());
            }
        }
    }

    private void addURLs(URL[] urls) {
        if (urls != null) {
            for (URL url : urls) {
                addURL(url);
            }
        }
    }

    private void addArchivo(File archivo) throws MalformedURLException {
        if (archivo != null) {
            addURL(archivo.toURI().toURL());
        }
    }

    private void addArchivo(String nombreArchivo) throws MalformedURLException {
        addArchivo(new File(nombreArchivo));
    }

    //-------------Arriba es puro copy/paste-----------------//
    
    private void createModel(String xmlFile) {
        boolean conn = this.modeloCrawler.setConnection(xmlFile);
        if (conn) {
            NodeList nList;
            if (modeloCrawler.getData().getLength() > 0) {
                nList = this.modeloCrawler.getData().item(0).getChildNodes();
            } else {
                return;
            }

            DefaultMutableTreeNode root = new DefaultMutableTreeNode(xmlFile);
            DefaultMutableTreeNode table = new DefaultMutableTreeNode("Datos");
            for (int temp = 0; temp < nList.getLength(); temp++) {
                Node nNode = nList.item(temp);
                if (nNode.getNodeType() == Node.ELEMENT_NODE) {
                    Element eElement = (Element) nNode;
                    DefaultMutableTreeNode column = getTreeChild(eElement);
                    table.add(column);
                }
            }
            root.add(table);
            modeloXML = new DefaultTreeModel(root);
        } else {
            System.out.println("No hay conexion con archivo " + xmlFile);
        }
    }
    
    private DefaultMutableTreeNode getTreeChild(Element father) {  
        if (father.getChildNodes().getLength() > 1) {
            DefaultMutableTreeNode arbol = new DefaultMutableTreeNode(father.getTagName());
         
            NodeList nList = father.getChildNodes();
            for(int i = 0; i<nList.getLength();i++){
                Node nNode = nList.item(i);
                if (nNode.getNodeType() == Node.ELEMENT_NODE) {
                    Element eElement = (Element) nNode;
                    DefaultMutableTreeNode column = getTreeChild(eElement);
                    arbol.add(column);
                }
            }
            return arbol;
        } else {
            DefaultMutableTreeNode arbol = new DefaultMutableTreeNode(father.getTagName() + " (tag)");
            return arbol;
        }

    }

    /**
     * 
     */
    @Override
    public void startCrawlerMode() {
        this.modeloCrawler = AbstractModelMapFactory.getAbstractModelMapFactory().getModelCrawler();

        this.vistaCrawler = AbstractViewMapFactory.getAbstractViewMapFactory().getVistaCrawler();
        this.vistaCrawler.setController(this);
        
        //se cargan los jars del directorio "plugins" al classpath
        boolean cargados = cargarPlugins();

        if (cargados) {
            try {
                //obtiene una instancia de cada plugin encontrado
                pluginLoaded = getPlugins();

                if (pluginLoaded.length > 0) {
                    for (ICrawlerPlugin a : pluginLoaded) {
                        this.vistaCrawler.setCrawlerName(a.getNameCrawler());
                    }
                } else {
                    
                }

            } catch (Exception ex) {
                System.err.println("Excepcion: " + ex.getMessage());
                ex.printStackTrace();
            }
        } else {
            System.out.println("Plugins No Cargados");
        }

        File[] xmlSource = buscarArchivos(DIRECTORIO_XML, EXTENSION_XML);

        if (xmlSource.length > 0) {
            for (File a : xmlSource) {
                this.vistaCrawler.setXMLName(a.getName());
            }
        } else {
            
        }
    }

    /**
     * 
     * @param xmlFile
     */
    @Override
    public void initMapCrawler(String xmlFile) {

        createModel(xmlFile);
        myController.mapFromCrawler(modeloCrawler, modeloXML);
    }

    /**
     * 
     * @param ControllerMainMapping
     */
    @Override
    public void setController(IControllerMainMapping ControllerMainMapping) {
        myController = ControllerMainMapping;
    }

    /**
     * 
     * @param selCrawler
     * @return
     */
    @Override
    public synchronized boolean initExtraction(int selCrawler) {
        idCrawler = selCrawler;
        if (pluginLoaded.length < 1) {
            return false;
        }
        controladorStats = AbstractControllerMapFactory.getAbstractControllerFactory().getControllerStats();
        controladorStats.setdataSource(pluginLoaded[selCrawler].getSourceData());
        controladorStats.setController(this);
        controladorStats.initStats();
        extractOK = pluginLoaded[selCrawler].initCrawling();
        
        return extractOK;

    }

    /**
     * 
     */
    @Override
    public void stopExtraction() {
         
         pluginLoaded[idCrawler].stopCrawler();
    }

    /**
     * 
     */
    @Override
    public void resumeExtraction() {
        hiloCrawler = new Thread() {

            public void run() {
                
                pluginLoaded[idCrawler].resumeCrawler();
            };
        };
        hiloCrawler.start();
    }
}
